home *** CD-ROM | disk | FTP | other *** search
- /*------------------------------------------------
- XMODEMR.C
-
- Author Date Description
- -------------------------------------------
- Jon Ward 22 Apr 90 Initial Revision.
- Jon Ward 23 Apr 90 Cleanup and modify for
- XMODEM-1K and XMODEM-CRC.
- Jon Ward 26 Apr 90 Corrected implementation
- of XMODEM-CRC.
- Jon Ward 7 Jun 90 Added more comments and a
- little cleanup.
- ------------------------------------------------*/
-
- #define XMODEM_LIB 1
-
- #include <stdio.h>
- #include "xmodem.h"
-
-
- #define STATIC static /* undef for debugging */
-
- /*------------------------------------------------
- ------------------------------------------------*/
- struct send_n_wait_st
- {
- char char_to_send;
- int retry_count;
- long ms_timeout;
- unsigned char *valid_responses;
- int num_valid_responses;
- };
-
- STATIC unsigned char soh_stx_can [] =
- { SOH, STX, CAN };
- STATIC unsigned char soh_stx_can_eot [] =
- { SOH, STX, CAN, EOT };
-
- STATIC struct send_n_wait_st crc_req =
- {
- 'C',
- CRC_RETRY_COUNT,
- CRC_TIMEOUT,
- soh_stx_can,
- sizeof (soh_stx_can)
- };
-
- STATIC struct send_n_wait_st checksum_req =
- {
- NAK,
- NAK_RETRY_COUNT,
- NAK_TIMEOUT,
- soh_stx_can,
- sizeof (soh_stx_can)
- };
-
- STATIC struct send_n_wait_st pack_nak =
- {
- NAK,
- NAK_RETRY_COUNT,
- NAK_TIMEOUT,
- soh_stx_can_eot,
- sizeof (soh_stx_can_eot)
- };
-
- STATIC struct send_n_wait_st pack_ack =
- {
- ACK,
- ACK_RETRY_COUNT,
- ACK_TIMEOUT,
- soh_stx_can_eot,
- sizeof (soh_stx_can_eot)
- };
-
-
- /*------------------------------------------------
- Error messages for error enums.
- ------------------------------------------------*/
- STATIC char *xmodem_errors [] =
- {
- "Transmission Successful",
- "NULL Transmit function pointer",
- "NULL Receive function pointer",
- "Receiver cancelled the transfer",
- "Sender cancelled the transfer",
- "User cancelled the transfer",
- "Error reading the file",
- "Error writing the file",
- "Timed out waiting for data pack ACK",
- "Timed out waiting for initial NAK",
- "Timed out waiting for SOH",
- "Timed out waiting for data",
- "Timed out waiting for final ACK",
- "Invalid char waiting for SOH",
- "Block mismatch in packet header",
- "CRC is incorrect",
- "Checksum is incorrect",
- "Block out of sequence",
- "Received character error",
- "Modem is not online",
- };
-
-
- /*------------------------------------------------
- Local Function Prototypes
- ------------------------------------------------*/
- STATIC int xm_send_n_wait (
- const struct send_n_wait_st *req, /* request structure */
- unsigned char *response, /* response from sender */
- xfunc *xmf); /* xmodem external functions */
-
- STATIC int xm_block_start (
- xblock *xb, /* xmodem block data */
- unsigned char block_start, /* block start char from sender */
- xfunc *xmf); /* xmodem external functions */
-
- STATIC int xm_recv_block (
- xblock *xb, /* xmodem block data */
- register xfunc *xmf); /* xmodem external functions */
-
-
- /*------------------------------------------------
- This function receives a file transferred via
- XMODEM, XMODEM-1K or XMODEM/CRC. The f argument
- represents the file to receive that has been
- opened for writing.
- ------------------------------------------------*/
- int xmodem_recv (
- FILE *f, /* file to write to */
- int (*transmit) (char), /* xmit function */
- int (*receive) (long, unsigned int *), /* recv function */
- void (*dispstat) (long, long, const char *), /* display function */
- int (*check_abort) (void)) /* manual abort function */
- {
- register int error; /* gen purpose error var */
- unsigned char start_char; /* first char of block */
- unsigned char next_block; /* next block we expect */
- unsigned char last_block; /* last successful block */
- xblock xb; /* xmodem block data */
- xfunc xmfuncs; /* xmodem external functions */
-
-
- /*------------------------------------------------
- Initialize the function pointer structure.
- ------------------------------------------------*/
- if ((xmfuncs.dispstat = dispstat) == NULL)
- xmfuncs.dispstat = xm_no_disp_func;
-
- if ((xmfuncs.check_abort = check_abort) == NULL)
- xmfuncs.check_abort = xm_no_abort_func;
-
- if ((xmfuncs.transmit = transmit) == NULL)
- return (xm_perror (XERR_XMIT_FUNC, &xmfuncs));
-
- if ((xmfuncs.receive = receive) == NULL)
- return (xm_perror (XERR_RCVR_FUNC, &xmfuncs));
-
-
- /*------------------------------------------------
- Initialize data for the first block and purge
- all data from the receive buffer. Init the
- number of bytes and blocks received and display
- some useful info.
- ------------------------------------------------*/
- next_block = last_block = 1;
-
- xb.total_block_count = 0L;
- xb.total_byte_count = 0L;
-
- (*xmfuncs.dispstat) (0L, 0L, "");
-
- PURGE_RECEIVER(receive);
-
-
- /*------------------------------------------------
- Attempt to transfer using CRC-16 error detection.
- This involves sending the CRC begin character:
- 'C'.
- ------------------------------------------------*/
- xb.crc_used = 1;
- error = xm_send_n_wait (&crc_req,
- &start_char,
- &xmfuncs);
-
-
- /*------------------------------------------------
- If the sender did not respond to the CRC-16
- transfer request, then attempt to transfer using
- checksum error detection.
- ------------------------------------------------*/
- if (error == XERR_SOH_TIMEOUT)
- {
- xb.crc_used = 0;
- error = xm_send_n_wait (&checksum_req,
- &start_char,
- &xmfuncs);
- }
-
-
- /*------------------------------------------------
- If begin transfer request failed, return error.
- ------------------------------------------------*/
- if (error != XERR_OK)
- return (error);
-
-
- /*------------------------------------------------
- If the starting character of the next block is
- an EOT, then we have completed transferring the
- file and we exit this loop. Otherwise, we init
- the xmodem packet structure based on the first
- character of the packet.
- ------------------------------------------------*/
- while (start_char != EOT)
- {
- register int good_block; /* NZ if packet was OK */
-
- error = xm_block_start (&xb,
- start_char,
- &xmfuncs);
-
- if (error != XERR_OK)
- return (error);
-
- good_block = -1; /* assume packet will be OK */
-
-
- /*------------------------------------------------
- Receive the packet. If there was an error, then
- NAK it. Otherwise, the packet was received OK.
- ------------------------------------------------*/
- if (xm_recv_block (&xb, &xmfuncs) != XERR_OK)
- {
- good_block = 0; /* bad block */
- }
-
- /*------------------------------------------------
- If this is the next expected packet, then append
- it to the file and update the last and next
- packet vars.
- ------------------------------------------------*/
- else if (xb.block_num == next_block)
- {
- int bytes_written; /* bytes written for this block */
-
- last_block = next_block;
- next_block = (next_block + 1) % 256;
-
- bytes_written = fwrite (xb.buffer, 1, xb.buflen, f);
-
- xb.total_block_count++;
- xb.total_byte_count += bytes_written;
-
- (*xmfuncs.dispstat) (xb.total_block_count,
- xb.total_byte_count,
- NULL);
-
- if (bytes_written != xb.buflen)
- {
- xm_send_cancel (transmit);
- return (xm_perror (XERR_FILE_WRITE, &xmfuncs));
- }
- }
-
-
- /*------------------------------------------------
- If this is the previous packet, then the sender
- did not receive our ACK to that packet and
- resent it. This is OK. Just ACK the packet.
-
- If the block number for this packet is completely
- out of sequence, cancel the transmission and
- return an error.
- ------------------------------------------------*/
- else if (xb.block_num != last_block)
- {
- xm_send_cancel (transmit);
- return (xm_perror (XERR_BLOCK_SEQUENCE, &xmfuncs));
- }
-
-
- /*------------------------------------------------
- Here, good_block is non-zero if the block was
- received and processed with no problems. If it
- was a good block, then we send an ACK. A NAK is
- sent for bad blocks.
- ------------------------------------------------*/
- if (good_block)
- {
- error = xm_send_n_wait (&pack_ack,
- &start_char,
- &xmfuncs);
- }
- else
- {
- PURGE_RECEIVER(receive);
- error = xm_send_n_wait (&pack_nak,
- &start_char,
- &xmfuncs);
- }
-
-
- if (error != XERR_OK)
- return (error);
- }
-
-
- /*------------------------------------------------
- The whole file has been received, so attempt to
- send an ACK to the final EOT.
- ------------------------------------------------*/
- if ((*transmit) (ACK) != XMIT_OK)
- return (xm_perror (XERR_OFFLINE, &xmfuncs));
-
- return (xm_perror (XERR_OK, &xmfuncs));
- }
-
-
- /*------------------------------------------------
- Dummy function used in case caller did not supply
- a display function.
- ------------------------------------------------*/
- void xm_no_disp_func (
- long a,
- long b,
- const char *buf)
- {
- a = a;
- b = b;
- buf = buf; /* avoid compiler warnings */
- }
-
-
- /*------------------------------------------------
- Dummy function used in case caller did not supply
- a used abort function.
- ------------------------------------------------*/
- int xm_no_abort_func (void)
- {
- return (0);
- }
-
-
- /*------------------------------------------------
- This function transmits a character and waits for
- a response. The req argument points to a
- structure containing info about the char to
- transmit, retry count, timeout, etc. The
- response argument points to a storage place for
- the received character. The xmf argument points
- to a structure of caller supplied functions.
-
- Any errors encountered are returned.
- ------------------------------------------------*/
- STATIC int xm_send_n_wait (
- const struct send_n_wait_st *req, /* request structure */
- unsigned char *response, /* response from sender */
- register xfunc *xmf) /* xmodem external functions */
- {
- int j;
- unsigned int rerr;
-
- for (j = 0; j < req->retry_count; j++)
- {
- register int rcvd_char;
- register int i;
-
- /*------------------------------------------------
- Check to see if the user aborted the transfer.
- ------------------------------------------------*/
- if ((*xmf->check_abort) () != 0)
- {
- xm_send_cancel (xmf->transmit);
- return (xm_perror (XERR_USER_CANCEL, xmf));
- }
-
-
- /*------------------------------------------------
- Transmit the block response (or block start)
- character.
- ------------------------------------------------*/
- if ((*xmf->transmit) (req->char_to_send) != XMIT_OK)
- return (xm_perror (XERR_OFFLINE, xmf));
-
-
- /*------------------------------------------------
- Wait for a response. If there isn't one or if a
- parity or similar error occurred, continue with
- next iteration of the retry loop.
- ------------------------------------------------*/
- rcvd_char = (*xmf->receive) (req->ms_timeout, &rerr);
-
- if (rcvd_char == RECV_TIMEOUT)
- continue;
-
- if (rerr != 0)
- return (xm_perror (XERR_CHAR_ERROR, xmf));
-
-
- /*------------------------------------------------
- Initialize the response and check to see if it is
- valid.
- ------------------------------------------------*/
- if (response != NULL)
- *response = (unsigned char) rcvd_char;
-
- for (i = 0; i < req->num_valid_responses; i++)
- if (rcvd_char == req->valid_responses [i])
- return (XERR_OK);
- }
-
- return (xm_perror (XERR_SOH_TIMEOUT, xmf));
- }
-
- /*------------------------------------------------
- This function analyzes valid block start
- characters to determine block size or in the case
- of CAN whether to abort the transmission.
-
- Any errors encountered are returned.
- ------------------------------------------------*/
- STATIC int xm_block_start (
- register xblock *xb, /* xmodem block data */
- unsigned char block_start, /* block start char from sender */
- register xfunc *xmf) /* xmodem external functions */
- {
- switch (block_start)
- {
- case SOH: /* NORMAL 128-byte block */
- xb->buflen = XMODEM_BLOCK_SIZE;
- return (XERR_OK);
-
- case STX: /* 1024-byte block */
- xb->buflen = XMODEM_1K_BLOCK_SIZE;
- return (XERR_OK);
-
- case CAN: /* Abort signal */
- if ((*xmf->receive) (CAN_TIMEOUT, NULL) == CAN)
- {
- xm_send_cancel (xmf->transmit);
- return (xm_perror (XERR_SEND_CANCEL, xmf));
- }
- break;
- }
-
- return (xm_perror (XERR_INVALID_SOH, xmf));
- }
-
-
- /*------------------------------------------------
- This function receives the block numbers, block
- data, and block checksum or CRC. The received
- data is stored in the structure pointed to by the
- xb argument. The block numbers are compared, and
- the checksum or CRC is verified.
-
- Any errors encountered are returned.
- ------------------------------------------------*/
- STATIC int xm_recv_block (
- xblock *xb, /* xmodem block data */
- register xfunc *xmf) /* xmodem external functions */
- {
- register int i;
- unsigned int rerr; /* receive error */
-
-
- /*------------------------------------------------
- Attempt to receive the block number. If the
- receiver timesout or if there is a receive error,
- ignore the rest of the packet.
- ------------------------------------------------*/
- if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
- return (xm_perror (XERR_DATA_TIMEOUT, xmf));
-
- if (rerr != 0)
- return (xm_perror (XERR_CHAR_ERROR, xmf));
-
- xb->block_num = (unsigned char) i;
-
-
- /*------------------------------------------------
- Attempt to receive the one's complement of the
- block number.
- ------------------------------------------------*/
- if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
- return (xm_perror (XERR_DATA_TIMEOUT, xmf));
-
- if (rerr != 0)
- return (xm_perror (XERR_CHAR_ERROR, xmf));
-
- xb->not_block_num = (unsigned char) i;
-
-
- /*------------------------------------------------
- Make sure that the block number and one's
- complemented block number agree.
- ------------------------------------------------*/
- if ((255 - xb->block_num) != xb->not_block_num)
- return (xm_perror (XERR_INVALID_BLOCK_NUM, xmf));
-
-
- /*------------------------------------------------
- Clear the CRC and checksum accumulators and
- receive the data block.
- ------------------------------------------------*/
- xb->crc = 0;
- xb->checksum = 0;
-
- for (i = 0; i < xb->buflen; i++)
- {
- int rcvd_char;
-
- if ((rcvd_char = (*xmf->receive) (DATA_TIMEOUT, &rerr)) ==
- RECV_TIMEOUT)
- return (xm_perror (XERR_DATA_TIMEOUT, xmf));
-
- if (rerr != 0)
- return (xm_perror (XERR_CHAR_ERROR, xmf));
-
- xb->buffer [i] = (unsigned char) rcvd_char;
-
- if (xb->crc_used != 0)
- xb->crc = xm_update_CRC (xb->crc, xb->buffer [i]);
- else
- xb->checksum += xb->buffer [i];
- }
-
-
- /*------------------------------------------------
- Validate the CRC.
- ------------------------------------------------*/
- if (xb->crc_used)
- {
- if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
- return (xm_perror (XERR_DATA_TIMEOUT, xmf));
-
- if (rerr != 0)
- return (xm_perror (XERR_CHAR_ERROR, xmf));
-
- if ((unsigned char) i != (unsigned char) (xb->crc >> 8))
- return (xm_perror (XERR_INVALID_CRC, xmf));
-
- if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
- return (xm_perror (XERR_DATA_TIMEOUT, xmf));
-
- if (rerr != 0)
- return (xm_perror (XERR_CHAR_ERROR, xmf));
-
- if ((unsigned char) i != (unsigned char) (xb->crc & 0xFF))
- return (xm_perror (XERR_INVALID_CRC, xmf));
- }
-
-
- /*------------------------------------------------
- Validate the checksum.
- ------------------------------------------------*/
- else
- {
- if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
- return (xm_perror (XERR_DATA_TIMEOUT, xmf));
-
- if (rerr != 0)
- return (xm_perror (XERR_CHAR_ERROR, xmf));
-
- if ((unsigned char) i != xb->checksum)
- return (xm_perror (XERR_INVALID_CHECKSUM, xmf));
- }
-
- return (XERR_OK);
- }
-
-
- /*------------------------------------------------
- This function prints an XMODEM status message
- using the caller supplied display function. The
- error argument is returned.
- ------------------------------------------------*/
- int xm_perror (
- int error, /* error number */
- xfunc *xmf) /* xmodem external functions */
- {
- xmf->dispstat (-1L, -1L, xmodem_errors [error]);
- return (error);
- }
-
- /*------------------------------------------------
- ------------------------------------------------*/
-
-
-